﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace ControlNode
{
    class PacketController
    {
        private Socket _server;
        private Thread _listeningForCall;
        private int _portTcp = 9785;
        private ASCIIEncoding encoder = new ASCIIEncoding();
        private readonly Dictionary<String, Socket> _nodes = new Dictionary<string, Socket>(); 



        private static ManualResetEvent AllDone = new ManualResetEvent(false);
        private static readonly ManualResetEvent SendDone = new ManualResetEvent(false);

        private NetworkCallController ncc;

        public delegate void LogEventHandler(string received);
        public static event LogEventHandler Log;

        public PacketController(NetworkCallController ncc)
        {
            this.ncc = ncc;
            ListenForCall();
        }

        private void ListenForCall()
        {
            try
            {
                _server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                var ipLocal = new IPEndPoint(IPAddress.Any, _portTcp);
                _server.Bind(ipLocal);
                _listeningForCall = new Thread(Call_Servicing);
                _listeningForCall.IsBackground = true;
                _listeningForCall.Start();
            }
            catch
            {
                MessageBox.Show("Błąd! Niespodziewana utrata połączenia!");
            }
        }

        private void Call_Servicing()
        {
            try
            {
                _server.Listen(4);
                while (true)
                {
                    // Set the event to nonsignaled state.
                    AllDone.Reset();
                    _server.BeginAccept(OnClientConnect, _server);
                    // Wait until a connection is made before continuing.
                    AllDone.WaitOne();
                }
            }
            catch (SocketException se)
            {
                _server.Close();
                MessageBox.Show(se.Message);
            }
        }

        public void OnClientConnect(IAsyncResult asyn)
        {
            try
            {
                // Signal the main thread to continue.
                AllDone.Set();
                Socket listener = (Socket)asyn.AsyncState;
                Socket handler = listener.EndAccept(asyn);

                StateObject stateObject = new StateObject() { WorkSocket = handler };
                handler.BeginReceive(stateObject.Buffer, 0, StateObject.BufferSize, SocketFlags.None, OnDataReceived, stateObject);
            }
            catch (ObjectDisposedException)
            {
                System.Diagnostics.Debugger.Log(0, "1", "\n OnClientConnection: Socket has been closed\n");
            }
            catch (SocketException se)
            {
                MessageBox.Show(se.Message);
            }
        }

        public void OnDataReceived(IAsyncResult asyn)
        {
            StateObject state = (StateObject)asyn.AsyncState;
            if (true)
            try
            {
                Socket handler = state.WorkSocket;
                // Read data from the client socket. 
                int bytesRead = handler.EndReceive(asyn);
                String received = encoder.GetString(state.Buffer);
                string[] tokens = received.Split(' ');
                if (tokens[0] == "login")
                {
                    _nodes.Add(tokens[1], handler);
                    Log("Podlaczylo sie urzadzenie" + tokens[1]);
                    Send("loginResponse C"+ ControlNode.CNodeID, handler);
                }
                else if (tokens[0] == "loginResponse")
                {
                    _nodes.Add(tokens[1], handler);
                    Log("Logged In "+tokens[1]);
                }
                else
                {
                    QueryHandler(received, handler);
                }

                // now start to listen for any data...
                handler.BeginReceive(state.Buffer, 0, StateObject.BufferSize, SocketFlags.None, OnDataReceived, state);
            }
            catch (ObjectDisposedException)
            {
                System.Diagnostics.Debugger.Log(0, "1", "\nOnDataReceived: Socket has been closed\n");
            }
            catch (SocketException se)
            {
                MessageBox.Show(se.Message);
            }
        }

        //tnie otrzymaną wiadomość, i w zależności od jej zawartości przekazuje do takiego elementu: CPCC, NCC, CC, RC, LRM
        public void QueryHandler(string received, Socket client)
        {
            string[] query = received.Split(' ');

            switch (query[0])
            {
                case "call_teardown":
                    ncc.CallTeardown(query[1], query[2]);
                    break;
                case "call_request":
                    //LOG("CPCC --> NCC");
                    ncc.CallRequest(query[1], query[2]);
                    break;
                default:
                    //MessageBox.Show("Nieprawidłowa wiadomość pomiędzy elementami warstwy sterowania! Treść wiadomości: " + received);
                    Log("Nieprawidłowa wiadomość pomiędzy elementami warstwy sterowania! Treść wiadomości: " + received);
                    break;
            }
        }

        public void Send(string message, Socket client)
        {
            client.BeginSend(encoder.GetBytes(message), 0, encoder.GetBytes(message).Length, SocketFlags.None, new AsyncCallback(SendCallback), client);
            SendDone.WaitOne();
        }

        private static void SendCallback(IAsyncResult ar)
        {
            try
            {
                // Retrieve the socket from the state object.
                Socket client = (Socket)ar.AsyncState;

                // Complete sending the data to the remote device.
                int bytesSent = client.EndSend(ar);

                // Signal that all bytes have been sent.
                SendDone.Set();
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }
        }

        public class StateObject
        {
            // Client  socket.
            public Socket WorkSocket { get; set; }
            // Size of receive buffer.
            public const int BufferSize = 1024 * 8;
            // Receive buffer.
            private byte[] buffer = new byte[BufferSize];
            public string Id { get; set; }

            public byte[] Buffer
            {
                get { return buffer; }
            }
        }
    }
}
